﻿using System;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Text.RegularExpressions;

using SystemX.Infrastructure;
using SystemX.WebControls.XPaging;
using System.Text;
using System.Web;
using SystemX.Web;

namespace SystemX.WebControls
{
	public class YPager : WebControl, IPostBackEventHandler
	{
		#region .ctor
		public YPager() {
			ItemsPerPage = 10;
			CurrentPage =0;
			MiddleLinks = 10;
			EdgeLinks = 0;
			PrevText = "Prev";
			NextText = "Next";
			EllipseText = "...";
			PageJumper = PageIndexBoxType.None;
			PageJumperText = "Page:";
			PageIndexQueryKey = "p";
		}
		#endregion

		#region properties & member variables
		public int TotalItems { get; set; }
		public int ItemsPerPage { get; set; }
		/// <summary>
		/// Current page index. Default is 0
		/// </summary>
		public int CurrentPage { get; set; }
		public int MiddleLinks { get; set; }
		public int EdgeLinks { get; set; }
		public string PrevText { get; set; }
		public string NextText { get; set; }
		public string EllipseText { get; set; }
		public bool ShowPrevAlways { get; set; }
		public bool ShowNextAlways { get; set; }
		public PageIndexBoxType PageJumper { get; set; }
		public string PageJumperText { get; set; }
		public string PageIndexQueryKey { get; set; }
		public string UrlRewritePattern { get; set; }
		/// <summary>
		/// url paging in new window.use window.open to open a new page.
		/// </summary>
		public bool UrlPagingNewWin { get; set; }
		public bool UrlPaging
		{
			get;
			set;
		}

		private static readonly object _SynHelper = new object();
		private static string _Html;
		private string OuterHtml {
			get
			{
				if (_Html == null) {
					lock (_SynHelper) {
						if (_Html == null) {
							_Html = loadLayout();
						}
					}
				}
				return _Html;
			}
		}
		protected bool ShowPageJumper {
			get
			{
				var yes =PageJumper!=PageIndexBoxType.None&&(TotalItems>1&&TotalItems>MiddleLinks);
				return yes;
			}
		}
		protected int TotalPages {
			get
			{
				var raw = (TotalItems / (double)ItemsPerPage);
				var retVal= ceil(raw);
				retVal = retVal < 1 ? 1 : retVal;
				return retVal;
			}
		}

		protected bool IsUrlRewriting {
			get
			{
				return !string.IsNullOrEmpty(UrlRewritePattern);
			}
		}
		protected Range<int> MiddleLinksRange {
			get
            {
				var r=new Range<int>();
				var ne_half = ceil(MiddleLinks / 2.0D);
				var upperLimits = TotalPages - MiddleLinks;
				r.Begin = CurrentPage> ne_half ? Math.Max(Math.Min(CurrentPage-ne_half,upperLimits), 0) : 0;
				r.End = CurrentPage> ne_half ? Math.Min(CurrentPage + ne_half, TotalPages) : Math.Min(MiddleLinks, TotalPages);
				return r;
            }
		}
		public int ItemsRemain
		{
			get
			{
				if (CurrentPage < (TotalPages - 1))
				{
					return (this.TotalItems - ((CurrentPage + 1) *ItemsPerPage));
				}
				return 0;
			}
		}
		/// <summary>
		/// Starting item index 
		/// </summary>
		public int StartIndex
		{
			get
			{
				return (CurrentPage *ItemsPerPage + 1);
			}
		}
		/// <summary>
		/// Ending item index
		/// </summary>
		public int EndIndex
		{
			get
			{
				return (TotalItems -ItemsRemain);
			}
		}
		#endregion

		#region base overrides
		protected override void OnInit(EventArgs e)
		{
			base.OnInit(e);
			ItemsPerPage = ItemsPerPage < 1 ? 1 : ItemsPerPage;
		}
		protected override void OnLoad(EventArgs e)
		{
			if (UrlPaging&&(!Page.IsPostBack)) {
				int num;
				int.TryParse(this.Page.Request.QueryString[this.PageIndexQueryKey], out num);
				if (num <1)
				{
					num = 1;
				}
				else if (num > TotalPages) {
					num = TotalPages;
				}
				CurrentPage = num - 1;
				OnPageChanging(new PageChangingEventArgs(CurrentPage));
			}
			base.OnLoad(e);
		}
		protected override void OnPreRender(EventArgs e)
		{
			ensurePostBackJS();
			Page.ClientScript.RegisterClientScriptInclude("Vivasky.XPager",
				Page.ClientScript.GetWebResourceUrl(this.GetType(), "SystemX.Assets.XPager.js")
				);
				
			base.OnPreRender(e);
		}
		protected override void Render(HtmlTextWriter writer)
		{
			writer.WriteLine(OuterHtml);
			appendClientScript(writer);
		}
		#endregion

		#region helper methods
		private int ceil(double x) {
			return Convert.ToInt32(Math.Ceiling(x));
		}
		private void toPage(int pageIndex) {
			CurrentPage = pageIndex;
			refreshLayout();
		}

		private void refreshLayout()
		{
			_Html = null;
		}
		private string loadLayout()
		{
			//CssClass resolve.
			CssClass = string.IsNullOrEmpty(CssClass) ? this.GetType().Name : CssClass;
			CssClass = string.Format("{0} {1}",CssClass,"clearfix");
			var div = new HtmlGenericControl("div");
			//add attributes
			div.Attributes["class"] = CssClass;
			div.Attributes["id"] = ID;
			//render contents
			//Generate "Previous"-Link
			if (!string.IsNullOrEmpty(PrevText) && (CurrentPage > 0 || ShowPrevAlways)) {
				appendItem(ref div, CurrentPage - 1, PrevText, "prev");
			};
			//Generate starting points
			if (MiddleLinksRange.Begin > 0 && EdgeLinks > 0) {
				for (int i = 0; i < Math.Min(EdgeLinks,MiddleLinksRange.Begin); i++)
				{
					appendItem(ref div, i, null, null);
				}
				if (EdgeLinks < MiddleLinksRange.Begin && !string.IsNullOrEmpty(EllipseText)) {
					appendEllipseItem(ref div);
				}
			};
			//Generate interval links
			for (int i = MiddleLinksRange.Begin; i <MiddleLinksRange.End; i++)
			{
				appendItem(ref div, i, null, null);
			};
			//Generate ending points
			if (MiddleLinksRange.End < TotalPages && EdgeLinks > 0) {
				if ((TotalPages - EdgeLinks) > MiddleLinksRange.End && !string.IsNullOrEmpty(EllipseText)) {
					appendEllipseItem(ref div);
				};
				for (int i = Math.Max(TotalPages-EdgeLinks,MiddleLinksRange.End); i < TotalPages; i++)
				{
					appendItem(ref div, i, null, null);
				}
			};
			//Generate "Next"-Link
			if (!string.IsNullOrEmpty(NextText) && (CurrentPage < TotalPages - 1 || ShowNextAlways)) {
				appendItem(ref div, CurrentPage + 1, NextText, "next");
			}
			//page jumper 
			appendPageJumper(ref div);
			//render
			using (StringWriter sw=new StringWriter())
			{
				using (HtmlTextWriter htw = new HtmlTextWriter(sw))
				{
					div.RenderControl(htw);
				}
				div.Dispose();
				return sw.ToString();
			}

		}
		/// <summary>
		/// Helper function for generating a single link (or a span tag if it's the current page)
		/// </summary>
		/// <param name="div"></param>
		/// <param name="page"></param>
		/// <param name="text"></param>
		/// <param name="cssClass"></param>
		private void appendItem(ref HtmlGenericControl div,int page,string text,string cssClass) {
			//Normalize page id to sane value
			page = page < 0 ? 0 : (page < TotalPages ? page : TotalPages - 1);
			text = text ?? (page + 1).ToString();
			using (var item = new HtmlGenericControl())
			{
				if (page == CurrentPage)
				{
					item.TagName = "span";
					item.Attributes["class"] = "current";
					item.InnerHtml = text;
				}
				else
				{
					item.TagName = "a";
					item.InnerHtml = text;
					if (UrlPaging)
					{
						item.Attributes["href"] = getUrlStr(page+1);
						if (UrlPagingNewWin) {
							item.Attributes["target"] = "_blank";
						}
					}
					else {
						item.Attributes["href"] = "#";//Page.ClientScript.GetPostBackClientHyperlink(this, page.ToString());
					};
					item.Attributes["id"] =string.Format("{0}_p{1}_{2}",ID,cssClass??"",page);
					if (!string.IsNullOrEmpty(cssClass))
					{
						item.Attributes["class"] = cssClass;
					}
				}
				div.Controls.Add(item);
			}
		}
		private void appendEllipseItem(ref HtmlGenericControl div) {
			using (var item = new HtmlGenericControl("span"))
			{
				item.InnerHtml = EllipseText;
				item.Attributes["class"] = "ellipse";
				div.Controls.Add(item);
			}
		}
		private void appendPageJumper(ref HtmlGenericControl div) {
			if (!ShowPageJumper) return;

			var wrapTexts = string.IsNullOrEmpty(PageJumperText) ? new string[] { } : PageJumperText.Split('|');

			var item = new HtmlGenericControl();
			item.Attributes["id"] = string.Format("{0}_jumper",ID);
			item.Attributes["name"] = string.Format("{0}_jumper", ID);
			item.Attributes["class"] = "jumper";
			switch (PageJumper)
			{
				case PageIndexBoxType.None:
					break;
				case PageIndexBoxType.DropDownList:
					item.TagName = "select";
					var optionItem=default(HtmlGenericControl);
					for (int i = 0; i <TotalPages; i++)
                    {
						optionItem= new HtmlGenericControl("option");
						optionItem.Attributes["value"] = i.ToString();
						optionItem.InnerHtml = (i + 1).ToString();
						if (CurrentPage == i) {
							optionItem.Attributes["selected"] = "selected";
						}
						item.Controls.Add(optionItem);
                    }
					break;
				case PageIndexBoxType.TextBox:
					item.TagName = "input";
					item.Attributes["type"] = "text";
					item.Attributes["value"] = (CurrentPage + 1).ToString();
					break;
			}
			if (wrapTexts.Length > 0) {
				using (var txt = new HtmlGenericControl("span"))
				{
					txt.Attributes["class"] = "jumper_label";
					txt.InnerHtml = wrapTexts[0];
					div.Controls.Add(txt);
				}
			};
			div.Controls.Add(item);
			if (wrapTexts.Length > 1) {
				using (var txt = new HtmlGenericControl("span"))
				{
					txt.Attributes["class"] = "jumper_label";
					txt.InnerHtml = wrapTexts[0];
					div.Controls.Add(txt);
				}
			};

			item.Dispose();
		}
		private void appendClientScript(HtmlTextWriter writer)
		{
			var opts =new { 
				pagerID=ID,
				pIndexJumperID=ShowPageJumper?string.Format("{0}_jumper",ID):null,
				uniqueID=this.UniqueID,
				pOutOfRangeTip="Page index is out of range!",//TODO:read from resources
				pInvalidTip="Page index is invalid!",
				pMax=TotalPages,
				urlPaging=UrlPaging,
				urlFormatStr=getUrlFormatStr(),
				urlPagingNewWin=UrlPagingNewWin
			};
			var js = new StringBuilder();
			js.AppendLine("<script type=\"text/javascript\">//<![CDATA[");
			js.AppendFormat("XPager.Init({0});", opts.ToJSONStr());
			js.AppendLine("//]]>\n</script>");

			//writer.Write(js.ToString());
			if (!Page.ClientScript.IsStartupScriptRegistered(this.GetType(), ID)) {
				Page.ClientScript.RegisterStartupScript(this.GetType(), ID, js.ToString());
			}
		}

		private string getUrlFormatStr()
		{
			var newUrl = string.Empty;
			var query = HttpUtility.ParseQueryString(this.Page.Request.Url.Query);
			if (!IsUrlRewriting)
			{
				query[PageIndexQueryKey] = "{0}";
				newUrl = String.Format("{0}?{1}", this.Page.Request.Url.AbsolutePath, query);
			}
			else
			{
				//Note:resolve url patterns like 'list_%flag%_%year%_{0}.aspx'
				//1,regex group 'p' is '%(?[^%]+)%'
				//2,regex group 'm' is '[^%]+'
				MatchCollection matchs = new Regex("(?<p>%(?<m>[^%]+)%)", RegexOptions.Compiled | RegexOptions.IgnoreCase).Matches(this.UrlRewritePattern);
				string urlRewritePattern = this.UrlRewritePattern;
				foreach (Match match in matchs)
				{
					string newValue = query[match.Groups["m"].Value];
					urlRewritePattern = urlRewritePattern.Replace(match.Groups["p"].Value, newValue);
				}
				newUrl = base.ResolveUrl(urlRewritePattern);
			};
			return HttpUtility.UrlDecode(newUrl);

		}
		private string getUrlStr(int pNum) {
			var urlFormatStr = getUrlFormatStr();
			var newUrl = string.Empty;
			newUrl = string.Format(urlFormatStr, pNum);
			return newUrl;

		}
		/// <summary>
		/// ensure the '__doPostBack' function was registered on the aspx page.
		/// </summary>
		/// <returns></returns>
		private void ensurePostBackJS() {
			var tempJS=Page.ClientScript.GetPostBackEventReference(this, "pIndex");
		}
		#endregion

		#region events
		public event EventHandler<PageChangingEventArgs> PageChanging;
		public event EventHandler PageChanged;
		#endregion

		#region methods
		protected void OnPageChanged() {
			refreshLayout();
			if (PageChanged != null) {
				PageChanged(this, new EventArgs());
			}
		}
		protected void OnPageChanging(PageChangingEventArgs e) {
			if (PageChanging != null)
			{
				PageChanging(this, e);
				if (!e.Cancel || UrlPaging)
				{
					CurrentPage = e.NewPageIndex;
					OnPageChanged();
				}
			}
			else {
				CurrentPage = e.NewPageIndex;
				OnPageChanged();
			}
		}
		#endregion

		#region IPostBackEventHandler Members

		public void RaisePostBackEvent(string eventArgument)
		{
			int currentPageIndex = CurrentPage;
			try
			{
				if (!string.IsNullOrEmpty(eventArgument))
				{
					currentPageIndex = int.Parse(eventArgument);
				}
				
			}
			catch
			{
			}
			var e = new PageChangingEventArgs(currentPageIndex);

			this.OnPageChanging(e);
		}

		#endregion
	}
}
